#define FUSE_USE_VERSION  26

//Fuse
#include <fuse.h>
#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <fcntl.h>
#include <stdlib.h>

//Consola
#include <pthread.h>

//Señal
#include <signal.h>

#define SIGKILL 9
#define TAM_MAX_LINEA 1024
#define FIN_OK	0
#define FIN_WARNING	1
#define FIN_ERROR	2
#define WARNING		2
#define INFO		3
#define ERROR		4
#define CMD_FSINFO	"fsinfo"
#define CMD_FINFO	"finfo"
#define CMD_SALIR	"salir"


struct CMD_FS_INFO{
	uint32_t cluster_ocupados;
	uint32_t cluster_libres;
	int tam_sector;
	int tam_cluster;
	int tam_fat;
}CMD_FS_INFO;


static const char *hello_str = "Hello Chiky World!\n";
static const char *hello_path = "/hello";

int imprimirLog(int a, char *b, char *c){
	printf("Log:\nTipo: %d\nTexto: %s %s\n",a,b,c);
	return 0;
}

static int hello_getattr(const char *path, struct stat *stbuf)
{
	int res = 0;

	////http://fuse.sourceforge.net/
	printf("Se solicito el atributo del path: %s\n",path);

	memset(stbuf, 0, sizeof(struct stat));

	if(strcmp(path, "/") == 0) {
		stbuf->st_mode = S_IFDIR | 0777;
		stbuf->st_nlink = 2;
	}
	else if(strcmp(path, hello_path) == 0) {
		stbuf->st_mode = S_IFREG | 0000;
		stbuf->st_nlink = 1;
		stbuf->st_size = strlen(hello_str);
	}
	else if(path[1] == 'a') {
		stbuf->st_mode = S_IFREG | 0777;
		stbuf->st_nlink = 1;
		stbuf->st_size =  10000;
	}
	else if(strcmp(path, "/Carpeta") == 0) {
		stbuf->st_mode = S_IFDIR | 0777;
		stbuf->st_nlink = 2;
	}
	else if(strcmp(path, "/Carpeta/a") == 0) {
		stbuf->st_mode = S_IFREG | 0777;
		stbuf->st_nlink = 1;
		stbuf->st_size = 10000;
	}
	else
		res = -ENOENT;

	return res;
}

static int hello_readdir(const char *path, void *buf, fuse_fill_dir_t filler,
		off_t offset, struct fuse_file_info *fi)
{
	(void) offset;
	(void) fi;

	char *nombre1;
	char *nombre2;
	char *nombreC1;

	nombre1=(char *)malloc(strlen("a")+1);
	nombre2=(char *)malloc(strlen("aaa")+1);
	nombreC1=(char *)malloc(strlen("Carpeta")+1);

	strcpy(nombre1,"a");
	strcpy(nombre2,"aaa");
	strcpy(nombreC1,"Carpeta");

	if(strcmp(path, "/") == 0){
		//Enviar archivos que contiene
		filler(buf, ".", NULL, 0);
		filler(buf, "..", NULL, 0);

		filler(buf, nombre1, NULL, 0);
		filler(buf, nombre2, NULL, 0);
		filler(buf, nombreC1, NULL, 0);
		filler(buf, "allo", NULL, 0);
		filler(buf, hello_path + 1, NULL, 0);

	}else if (strcmp(path, "/Carpeta") == 0){
		filler(buf, ".", NULL, 0);
		filler(buf, "..", NULL, 0);
		filler(buf, nombre1, NULL, 0);
	}else
		return -ENOENT;

	//liberar memoria
	free(nombre1);
	free(nombre2);

	return 0;
}

static int hello_open(const char *path, struct fuse_file_info *fi)
{
	if(strcmp(path, hello_path) != 0 && path[1]!='a' && strcmp(path, "/Carpeta/a") != 0)
		return -ENOENT;

	////verificar que tiene permisos
	if((fi->flags & 3) != O_RDONLY)
		return -EACCES;

	return 0;
}

static int hello_read(const char *path, char *buf, size_t size, off_t offset,
		struct fuse_file_info *fi)
{

	////Investigar parametros
	int i;
	char archivoA[10000];

	size_t len;
	(void) fi;
	if(strcmp(path, hello_path) != 0 && path[1]!='a'  && strcmp(path, "/Carpeta/a") != 0)
		return -ENOENT;

	if(strcmp(path, hello_path)==0){
		len = strlen(hello_str);
		if (offset < len) {
			if (offset + size > len)
				size = len - offset;
			memcpy(buf, hello_str + offset, size);
		} else
			size = 0;
	}else{

		//Generar contenido del archivo grande
		strcpy(archivoA,"Contenido del archivo:\n");
		for (i=strlen(archivoA);i<10000;i++){
			archivoA[i]='.';
		}
		archivoA[10000-1]='\n';


		len = 10000;
		if (offset < len) {
			if (offset + size > len)
				size = len - offset;
			memcpy(buf, archivoA + offset, size);
		} else
			size = 0;
	}

	return size;
}

static int hello_mkdir(const char *path, mode_t mode)
{
	int res;

	////Retornar 0 para OK y <>0 para error

	return -1;
	////No continuar

	res = mkdir(path, mode);
	if (res == -1)
		return -errno;

	return 0;
}

static int hello_write(const char *path, const char *buf, size_t size,
		off_t offset, struct fuse_file_info *fi)
{
	int fd;
	int res;

	(void) fi;
	fd = open(path, O_WRONLY);
	if (fd == -1)
		return -errno;

	res = pwrite(fd, buf, size, offset);
	if (res == -1)
		res = -errno;

	close(fd);
	return res;
}

static int hello_release(const char *path, struct fuse_file_info *fi)
{
	/* Just a stub.	 This method is optional and can safely be left
  	   unimplemented */

	(void) path;
	(void) fi;
	return 0;
}

static int hello_truncate(const char *path, off_t size)
{
	int res;

	res = truncate(path, size);
	if (res == -1)
		return -errno;

	return 0;
}

static int hello_unlink(const char *path)
{
	int res;

	res = unlink(path);
	if (res == -1)
		return -errno;

	return 0;
}

static int hello_rmdir(const char *path)
{
	int res;

	res = rmdir(path);
	if (res == -1)
		return -errno;

	return 0;
}

static int hello_rename(const char *from, const char *to)
{
	int res;

	res = rename(from, to);
	if (res == -1)
		return -errno;

	return 0;
}

static struct fuse_operations hello_oper = {
		.getattr   = hello_getattr,
		.readdir = hello_readdir,
		.open   = hello_open,
		.read   = hello_read,
		.mkdir = hello_mkdir,
		.write = hello_write,
		.release = hello_release,
		.truncate = hello_truncate,
		.unlink	= hello_unlink,
		.rmdir = hello_rmdir,
		.rename = hello_rename

		//.flush = hello_flush,
		//.create = hello_create
};


void atenderSignal(){
	///Desarrollar

	printf("SEÑAL\n");
}

int obtenerComando(char *linea, char **comando, char **Parametro){

	imprimirLog(INFO, "Ingrese un comando:","");
	fgets(linea, TAM_MAX_LINEA, stdin);

	*comando= strtok(linea, " \n");
	*Parametro= strtok(NULL, " \n");

	return FIN_OK;
}

int cmdInfoFS(){
	//Información sobre el File System

	struct CMD_FS_INFO info;
	char texto[TAM_MAX_LINEA];

	////Obtener info
	info.cluster_ocupados=10;
	info.cluster_libres=20;
	info.tam_sector=512;
	info.tam_cluster=info.tam_sector*8;
	info.tam_fat=10000;
	////

	sprintf(texto,"Clusters ocupados: %u", info.cluster_ocupados);
	imprimirLog(INFO,texto,"");
	sprintf(texto,"Clusters libres: %u", info.cluster_libres);
	imprimirLog(INFO,texto,"");
	sprintf(texto,"Tamaño de un sector: %u bytes", info.tam_sector);
	imprimirLog(INFO,texto,"");
	sprintf(texto,"Tamaño de un cluster: %u bytes", info.tam_cluster);
	imprimirLog(INFO,texto,"");
	sprintf(texto,"Tamaño de la FAT: %u kilobytes", info.tam_fat);
	imprimirLog(INFO,texto,"");

	return FIN_OK;
}

int cmdInfoArchivo(char *rutaArc){
	//Información sobre el File System

	uint32_t clusters[19];
	char texto[TAM_MAX_LINEA];
	int i;

	//Validar ruta
	if (!rutaArc){
		imprimirLog(INFO,"Escriba la ruta del archivo","");
		return FIN_WARNING;
	}

	////Obtener los primero 20 clusters
	for(i=0; i<15; i++){
		clusters[i]=(i+2)*2;
	}
	clusters[15]=-1;
	////

	strcpy(texto,"[");
	if (clusters[0]!=-1){
		sprintf(texto,"%s%u",texto,clusters[0]);
		for (i=0;i<19;i++){
			if (clusters[i]!=-1)
				sprintf(texto,"%s, %u",texto,clusters[i]);
			else
				break;
		}
	}
	sprintf(texto,"%s]",texto);

	imprimirLog(INFO,texto,"");

	return FIN_OK;
}

void atencionConsola(void *ptr){
	char linea[TAM_MAX_LINEA];
	char *comando;
	char *path;
	char *textoSalir;

	signal( SIGUSR1, &atenderSignal );
	imprimirLog(INFO,"Señal inicializada","");


	printf("PID: %d\n",getpid());	////Con bib_log no sera necesario


	imprimirLog(INFO,"Consola inicializada","");

	while(1){
		if (!obtenerComando(linea, &comando, &path)){

			//Validar
			if(comando){

				if (!strcmp(comando,CMD_FSINFO)){
					if (cmdInfoFS() == FIN_ERROR)
						break;

				}else if (!strcmp(comando, CMD_FINFO)){
					if (cmdInfoArchivo(path) == FIN_ERROR)
						break;
				}
				else if (!strcmp(comando, CMD_SALIR)){
					//Desmontar
					textoSalir=(char *)malloc(strlen("fusermount -u ") + strlen((char *)ptr)+1);
					sprintf(textoSalir,"fusermount -u %s",(char *)ptr);
					system(textoSalir);
					free(textoSalir);

					break;
				}else
					imprimirLog(WARNING, "Escriba alguno de los siguientes comandos: fsinfo, finfo [Archivo] o salir. Comando invalido:", comando);
			}

		}
	}

	printf("La consola finalizo\n");

	return;
}

int main(int argc, char *argv[])
{
	pthread_t hiloConsola;

	//Verificar parámetro
	if (argc<1){
		imprimirLog(ERROR,"Ingrese el punto de montaje.","");
		return FIN_ERROR;
	}

	pthread_create((pthread_t *)&hiloConsola, NULL, (void *) &atencionConsola, (void *) argv[1]);

	if (fuse_main(argc, argv, &hello_oper, NULL)){
		printf("Error al iniciar Fuse\n");
		pthread_kill(hiloConsola,SIGKILL);
	}

	//Esperar a que termine el hilo
	pthread_join(hiloConsola, NULL);

	return FIN_OK;
}
